using System;
using BlobSim.Renderers;
using BlobSim.Shapes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace BlobSim
{
    public class BlobSim : Game
    {
        readonly GameState _gameState;
        SkyBox _skyBox;
        Shape[] _shapes;
        int _shapeIdx;
        ReflectiveSurfaceRenderer _renderer;

        public BlobSim()
        {
            new GraphicsDeviceManager(this)
                {
                    IsFullScreen = true,
                    PreferredBackBufferWidth = 480,
                    PreferredBackBufferHeight = 800,
                    PreferredBackBufferFormat = SurfaceFormat.Color
                };

            Content.RootDirectory = "Content";

            TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 60);
            InactiveSleepTime = TimeSpan.FromSeconds(1);
            TouchPanel.EnabledGestures = GestureType.DoubleTap;
            _gameState = new GameState();
        }

        protected override void Initialize()
        {
            _skyBox = new SkyBox(GraphicsDevice, Content, "CubemapCross");
            _shapes = new Shape[4];
            _shapeIdx = 0;
            _shapes[0] = new Cube(GraphicsDevice, 1.8f, 4);
            _shapes[1] = new Sphere(GraphicsDevice, 1.3f, 32, 16);
            _shapes[2] = new Torus(GraphicsDevice, 0.7f, 1.4f, 32, 32);
            _shapes[3] = new Cylinder(GraphicsDevice, 0.8f, 32, 2.4f, 8);
            _renderer = new ReflectiveSurfaceRenderer(GraphicsDevice, Content, "CubemapCross");

            base.Initialize();
        }

        protected override void LoadContent()
        {
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            HandleInput(gameTime);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            var fieldOfView = MathHelper.ToRadians(60.0f);
            var aspectRatio = GraphicsDevice.Viewport.AspectRatio;
            const float nearPlane = 0.1f;
            const float farPlane = 100.0f;

            GraphicsDevice.Clear(Color.CornflowerBlue);

            _skyBox.SetOrientation(_gameState.CameraOrientation);
            _skyBox.SetProjection(fieldOfView, aspectRatio);
            _skyBox.Draw();

            var worldMatrix = _gameState.ObjectRotation.GetRotationMatrix();
            
            var viewMatrix =
                Utils.CalcLookAt(_gameState.CameraOrientation.HorizontalAngle, _gameState.CameraOrientation.VerticalAngle) *
                Matrix.CreateLookAt(_gameState.CameraDistance * Vector3.Backward, Vector3.Zero, Vector3.Up);
            
            var projMatrix = Matrix.CreatePerspectiveFieldOfView(fieldOfView, aspectRatio, nearPlane, farPlane);

            _renderer.SetMatrices(worldMatrix, viewMatrix, projMatrix);
            _renderer.Draw(_shapes[_shapeIdx]);

            base.Draw(gameTime);
        }

        private void HandleInput(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            {
                Exit();
            }
            while (TouchPanel.IsGestureAvailable)
            {
                var gesture = TouchPanel.ReadGesture();
                if (gesture.GestureType == GestureType.DoubleTap)
                {
                    _shapeIdx = (_shapeIdx + 1) % _shapes.Length;
                }
            }
            var touches = TouchPanel.GetState();
            switch (touches.Count)
            {
                case 0:
                    _gameState.ObjectRotation.UpdateRotation(gameTime);
                    break;
                case 1:
                    {
                        var touch = touches[0];
                        switch (touch.State)
                        {
                            case TouchLocationState.Pressed:
                                _gameState.ObjectRotation.PanRotate(Vector2.Zero, Vector3.UnitZ, gameTime);
                                break;
                            case TouchLocationState.Moved:
                                var prevTouch = Utils.GetPreviousTouchLocation(touch);
                                var delta = touch.Position - prevTouch.Position;
                                delta /= _gameState.RotationSwipeResolutionFactor;
                                var cameraOrientation = _gameState.CameraOrientation.ToVector();
                                _gameState.ObjectRotation.PanRotate(delta, cameraOrientation, gameTime);
                                break;
                            case TouchLocationState.Released:
                                break;
                        }
                        break;
                    }

                case 2:
                    {
                        var touchA = touches[0];
                        var touchB = touches[1];
                        if (touchA.State == TouchLocationState.Moved &&
                            touchB.State == TouchLocationState.Moved)
                        {
                            var prevTouchA = Utils.GetPreviousTouchLocation(touchA);
                            var prevTouchB = Utils.GetPreviousTouchLocation(touchB);
                            var deltaVector = touchA.Position - touchB.Position;
                            var prevDeltaVector = prevTouchA.Position - prevTouchB.Position;

                            var zoomPercent =
                                (deltaVector).Length() /
                                (prevDeltaVector).Length();
                            _gameState.CameraDistance /= zoomPercent;

                            var cameraOrientation = _gameState.CameraOrientation.ToVector();

                            var twistAngle = Utils.GetAngle(deltaVector, prevDeltaVector);
                            if (twistAngle != 0.0)
                            {
                                var cross = Vector3.Cross(new Vector3(deltaVector, 0.0f), new Vector3(prevDeltaVector, 0.0f));
                                twistAngle *= Math.Sign(cross.Z);
                                _gameState.ObjectRotation.TwistRotate(-cameraOrientation, twistAngle, gameTime);
                            }

                            var middle = (touchA.Position + touchB.Position) / 2;
                            var prevMiddle = (prevTouchA.Position + prevTouchB.Position) / 2;
                            var panDelta = (middle - prevMiddle) / _gameState.RotationSwipeResolutionFactor;
                            _gameState.ObjectRotation.PanRotate(panDelta, cameraOrientation, gameTime);
                        }
                        break;
                    }

                case 3:
                    {
                        var touchA = touches[0];
                        var touchB = touches[1];
                        var touchC = touches[2];
                        if (touchA.State == TouchLocationState.Moved &&
                            touchB.State == TouchLocationState.Moved &&
                            touchC.State == TouchLocationState.Moved)
                        {
                            var prevTouchA = Utils.GetPreviousTouchLocation(touchA);
                            var prevTouchB = Utils.GetPreviousTouchLocation(touchB);
                            var prevTouchC = Utils.GetPreviousTouchLocation(touchC);
                            
                            var middleTouch = (touchA.Position + touchB.Position + touchC.Position) / 3;
                            var prevMiddleTouch = (prevTouchA.Position + prevTouchB.Position + prevTouchC.Position) / 3;

                            var delta = middleTouch - prevMiddleTouch;

                            delta /= _gameState.CameraSwipeResolutionFactor;
                            _gameState.CameraOrientation.AdjustOrientation(delta, gameTime);
                        }
                    }
                    break;
                
                default:
                    break;
            }
        }
    }
}
